package cn.yangliu.jpa.handler;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.TypedQuery;

import cn.yangliu.jpa.annotations.jpql.JpaJpql;
import cn.yangliu.jpa.annotations.jpql.JpaJpqlQuery;
import cn.yangliu.jpa.enums.LinkTypeEnum;
import cn.yangliu.jpa.enums.QueryTypeEnum;
import cn.yangliu.jpa.tools.JpaUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Sort;

/**
 * The interface Param transformer.
 *
 * @author 问道于盲
 * @date 2019-06-14
 */
class JpqlHandler {

    /**
     * current class's static member
     * The constant log.
     */
    private static final Logger log = LoggerFactory.getLogger(JpqlHandler.class);

    /**
     * current class's static member
     * The constant THREAD_LOCAL.
     */
    private static final ThreadLocal<Boolean> THREAD_LOCAL = new ThreadLocal<>();

    /**
     * Instantiates a new Jpql handler.
     * forbid other class to create a new instance.
     * only itself can create a new instance.
     */
    private JpqlHandler() {
    }


    /**
     * 构建jpal查询对象
     *
     * @param <T>                  实体类型
     * @param entityManagerFactory entityManagerFactory
     * @param queryPojo            查询实体对象封装
     * @param sort                 the sort
     * @return 查询对象 typed query
     */
    static <T> TypedQuery<T> buildJpqlQuery(EntityManagerFactory entityManagerFactory, Object queryPojo, Sort sort) {

        String sourceJpql = JpaUtils.getSourceJpql(queryPojo);

        Class<T> clazz = JpaUtils.getEntityClass(queryPojo);

        List<String> orders = new ArrayList<>();
        List<String> directions = new ArrayList<>();
        JpaUtils.buildOrders(sort, orders, directions);

        String orderBy = JpaUtils.buildOrderBy(orders, directions);

        List<Object> params = new ArrayList<>();
        List<String> fields = new ArrayList<>();

        String jpql = buildJpql(sourceJpql, queryPojo, params, fields, orderBy);

        log.info("excute jpql : " + jpql);

        return buildQueryParameters(entityManagerFactory, jpql, clazz, params, fields);
    }




    /**
     * 给jpql查询语句设置参数 并返回jpa查询对象
     *
     * @param <T>                  the type parameter
     * @param entityManagerFactory the entity manager factory
     * @param jpql                 the jpql
     * @param queryPojo            the queryPojo
     * @param params               the params
     * @param fields               the fields
     * @return the typed query
     */
    static <T> TypedQuery<T> buildQueryParameters(EntityManagerFactory entityManagerFactory, String jpql,
                                                  Object queryPojo, List<Object> params, List<String> fields) {
        JpaJpql jpaJpql = queryPojo.getClass().getDeclaredAnnotation(JpaJpql.class);
        Class clazz = jpaJpql.resultClass();
        return buildQueryParameters(entityManagerFactory, jpql, clazz, params, fields);
    }


    /**
     * 给jpql查询语句设置参数 并返回jpa查询对象
     *
     * @param <T>                  the type parameter
     * @param entityManagerFactory the entity manager factory
     * @param jpql                 the jpql
     * @param clazz                the clazz
     * @param params               the params
     * @param fields               the fields
     * @return the typed query
     */
    static <T> TypedQuery<T> buildQueryParameters(EntityManagerFactory entityManagerFactory, String jpql,
                                                  Class<T> clazz, List<Object> params, List<String> fields) {
        EntityManager entityManager = entityManagerFactory.createEntityManager();
        TypedQuery<T> query = entityManager.createQuery(jpql, clazz);
        for (int i = 0; i < fields.size(); i++) {
            String fieldName = fields.get(i);
            Object value = params.get(i);
            query.setParameter(fieldName, value);
        }
        return query;
    }

    /**
     * 给jpql拼上占位参数
     * 如 'from User where username = :username and age = :age'
     *
     * @param sb              the sb
     * @param jpaJpqlQuery    the jpa query
     * @param targetFieldName the field name
     * @param value           the value
     */
    private static void buildParams(StringBuilder sb, JpaJpqlQuery jpaJpqlQuery,
                                    String targetFieldName, Object value) {
        String parameter = jpaJpqlQuery.parameter();
        if (StringUtils.isBlank(parameter)) {
            parameter = targetFieldName;
        }
        LinkTypeEnum linkType = jpaJpqlQuery.link();
        //and or
        String linkString = JpaUtils.linkType(linkType, THREAD_LOCAL.get()) + " ";
        QueryTypeEnum queryTypeEnum = jpaJpqlQuery.type();
        String queryType = queryTypeEnum.symbol();
        if (Objects.equals(QueryTypeEnum.LIKE, queryTypeEnum)) {
            sb.append(linkString)
                    .append(targetFieldName)
                    .append(" ")
                    .append(queryType)
                    .append(" :")
                    .append(parameter);
        } else if (Objects.equals(QueryTypeEnum.STARTWITH, queryTypeEnum)) {
            sb.append(linkString)
                    .append(targetFieldName)
                    .append(" ")
                    .append(queryType)
                    .append(" :")
                    .append(parameter);
        } else if (Objects.equals(QueryTypeEnum.ENDWITH, queryTypeEnum)) {
            sb.append(linkString)
                    .append(targetFieldName)
                    .append(" ")
                    .append(queryType)
                    .append(" :")
                    .append(parameter);
        } else if (Objects.equals(QueryTypeEnum.EQ, queryTypeEnum)) {
            sb.append(linkString)
                    .append(targetFieldName)
                    .append(" ")
                    .append(queryType)
                    .append(" :")
                    .append(parameter);
        } else if (Objects.equals(QueryTypeEnum.LT, queryTypeEnum)) {
            sb.append(linkString)
                    .append(targetFieldName)
                    .append(" ")
                    .append(queryType)
                    .append(" :")
                    .append(parameter);
        } else if (Objects.equals(QueryTypeEnum.GT, queryTypeEnum)) {
            sb.append(linkString)
                    .append(targetFieldName)
                    .append(" ")
                    .append(queryType)
                    .append(" :")
                    .append(parameter);
        } else if (Objects.equals(QueryTypeEnum.LTE, queryTypeEnum)) {
            sb.append(linkString)
                    .append(targetFieldName)
                    .append(" ")
                    .append(queryType)
                    .append(" :")
                    .append(parameter);
        } else if (Objects.equals(QueryTypeEnum.GTE, queryTypeEnum)) {
            sb.append(linkString)
                    .append(targetFieldName)
                    .append(" ")
                    .append(queryType)
                    .append(" :")
                    .append(parameter);
        } else if (Objects.equals(QueryTypeEnum.NOTEQ, queryTypeEnum)) {
            sb.append(linkString)
                    .append(targetFieldName)
                    .append(" ")
                    .append(queryType)
                    .append(" :")
                    .append(parameter);
        } else if (Objects.equals(QueryTypeEnum.IN, queryTypeEnum)) {
            if (JpaUtils.isNotEmptyArray(value)) {
                sb.append(linkString)
                        .append(targetFieldName)
                        .append(" ")
                        .append(queryType);
                JpaUtils.buildArrayParams(sb, value);
            }
        }
    }

    /**
     * Build jpql string.
     *
     * @param sourceJpql the source jpql
     * @param queryPojo  the query pojo
     * @param params     the params
     * @param fields     the fields
     * @param orderBy    the orderBy
     * @return the string
     */
    static String buildJpql(String sourceJpql, Object queryPojo, List<Object> params, List<String> fields,
                            String orderBy) {
        StringBuilder sb = new StringBuilder(sourceJpql);
        List<Field> fieldList = JpaUtils.getDeclaredFields(queryPojo);
        THREAD_LOCAL.set(false);
        fieldList = JpaUtils.sort(fieldList, JpaJpqlQuery.class);
        for (Field field : fieldList) {
            Object value = JpaUtils.getFieldValue(queryPojo, field);
            if (Objects.isNull(value) || Objects.equals(value, "")) {
                continue;
            }
            JpaJpqlQuery jpaJpqlQuery = field.getDeclaredAnnotation(JpaJpqlQuery.class);

            if (Objects.isNull(jpaJpqlQuery)) {
                continue;
            }
            QueryTypeEnum queryTypeEnum = jpaJpqlQuery.type();

            if (Objects.equals(QueryTypeEnum.LIKE, queryTypeEnum)) {
                value = "%" + value.toString() + "%";
            }
            if (Objects.equals(QueryTypeEnum.STARTWITH, queryTypeEnum)) {
                value = value.toString() + "%";
            }
            if (Objects.equals(QueryTypeEnum.ENDWITH, queryTypeEnum)) {
                value = "%" + value.toString();
            }
            if (!THREAD_LOCAL.get()) {
                sb.append(JpaUtils.WHERE);
            }

            String[] fieldNames = jpaJpqlQuery.fields();

            if (fieldNames.length == 0) {
                String targetFieldName = JpaUtils.targetFieldName(jpaJpqlQuery, field);
                buildParams(sb, jpaJpqlQuery, targetFieldName, value);
                THREAD_LOCAL.set(true);
                fields.add(targetFieldName);
                params.add(value);
            } else {
                String linkType = JpaUtils.linkType(jpaJpqlQuery.link(), THREAD_LOCAL.get());
                sb.append(linkType);
                sb.append("( ");
                String innerQuery = buildInnerQuery(fields, jpaJpqlQuery, field, params, value);
                sb.append(innerQuery);
                sb.append(") ");
                THREAD_LOCAL.set(true);
            }

        }

        if (StringUtils.isNotBlank(orderBy)) {
            if (!orderBy.contains("order by") && !orderBy.contains("ORDER BY")) {
                sb.append(" order by ");
            }
            sb.append(" ").append(orderBy);
        }

        THREAD_LOCAL.remove();

        return sb.toString();
    }


    /**
     * 构建组合查询(同一参数值的情况)
     * 如(name in ('zhangfei','guanyu', 'liubei') or nickname in ('zhangfei','guanyu', 'liubei'))
     * 其中'zhangfei','guanyu', 'liubei' 是同一参数值 查询属性name 和 nickname是一个组合
     *
     * @param fields       the fields
     * @param jpaJpqlQuery the jpa jpql query
     * @param field        the field
     * @param params       the params
     * @param value        the value
     * @return the string
     */
    private static String buildInnerQuery(List<String> fields, JpaJpqlQuery jpaJpqlQuery, Field field,
										  List<Object> params, Object value) {
        LinkTypeEnum innerLinkType = jpaJpqlQuery.innerLink();
        String[] targetFieldNames = jpaJpqlQuery.fields();
        String filedName = field.getName();
        String symbol = jpaJpqlQuery.type().symbol() + " ";
        StringBuilder arrayParams = new StringBuilder();

        StringBuilder sb = new StringBuilder();
        boolean begin = true;
        for (String targetFieldName : targetFieldNames) {
            if (value.getClass().isArray() || value instanceof Collection) {
                if (begin) {
                    begin = false;
                } else {
                    arrayParams.append(innerLinkType.name()).append(" ");
                }
                arrayParams.append(targetFieldName);
                arrayParams.append(" ").append("IN");
                JpaUtils.buildArrayParams(arrayParams, value);
                continue;
            }

            fields.add(JpaUtils.targetFieldName(jpaJpqlQuery, field));
            if (begin) {
                begin = false;
            } else {
                sb.append(innerLinkType.name()).append(" ");
            }

            sb.append(targetFieldName)
                    .append(" ")
                    .append(symbol)
                    .append(":")
                    .append(filedName).append(" ");
            params.add(value);
        }

        sb.append(arrayParams.toString());

        return sb.toString();
    }
}
